package com.atguigu.daijia.order.testLock;

import com.alibaba.cloud.commons.lang.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TestServiceImpl implements TestService{

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 使用 分布式锁 setnx + lua  不能解决锁续期的问题
     */
    @Override
    public void testLock() {
        //从redis里面获取数据
        //1 获取当前锁  setnx   如果存在 就返回false ｜  如果不存在 就返回true
        //1 获取当前锁  setnx  + 设置过期时间  为了防止各种原因导致死锁，锁一致不释放
        String uuid = UUID.randomUUID().toString();
        //1 获取当前锁  setnx  + 设置过期时间 +唯一id，为了防止自己释放锁的时候 释放其他线程的锁
        // Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
        Boolean ifAbsent =
                redisTemplate.opsForValue()
                        .setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
        //2 如果获取到锁，从redis获取数据 数据+1 放回redis里面
        if(ifAbsent) {
            log.info("Thread=>{},获取锁成功，执行业务代码",Thread.currentThread().getName());
            //获取锁成功，执行业务代码
            //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
            String value = redisTemplate.opsForValue().get("num").toString();
            log.info("Thread=>{},从redis里面获取数据=>{}",Thread.currentThread().getName(),value);
            //2.如果值为空则非法直接返回即可
            if (StringUtils.isBlank(value)) {
                return;
            }
            //3.对num值进行自增加一
            int num = Integer.parseInt(value);
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
            log.info("Thread=>{},把从redis获取数据+1=>{}",Thread.currentThread().getName(),num);

            //3 释放锁
            String redisUuid = redisTemplate.opsForValue().get("lock").toString();
            if(uuid.equals(redisUuid)) {
                redisTemplate.delete("lock");
            }
            log.info("Thread=>{},释放锁=>{}",Thread.currentThread().getName(),"lock");
        } else {
            try {
                log.info("Thread=>{},未抢到锁，开始=>{}",Thread.currentThread().getName(),"等待100s");
                Thread.sleep(100);
                log.info("Thread=>{},再次尝试获取锁",Thread.currentThread().getName());
                this.testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 使用 JVM本地锁   分布式就gg
     */
    public synchronized void testLock01() {

        //从redis里面获取数据
        String value =  Objects.requireNonNull(redisTemplate.opsForValue().get("num")).toString();
        log.info("Thread=>{},从redis里面获取数据=>{}",Thread.currentThread().getName(),value);
        if(StringUtils.isBlank(value)) {
            return;
        }

        //把从redis获取数据+1
        int num = Integer.parseInt(value);
        log.info("Thread=>{},把从redis获取数据+1=>{}",Thread.currentThread().getName(),num);

        //数据+1之后放回到redis里面
        redisTemplate.opsForValue().set("num",String.valueOf(++num));
        log.info("Thread=>{},数据+1之后放回到redis里面=>{}",Thread.currentThread().getName(),num);

    }



    @Autowired
    private RedissonClient redissonClient;

    //Redisson实现   保证原子性 并且锁自动续期
    public void testLockUseRedisson()  {

        //1 通过redisson创建锁对象
        RLock lock = redissonClient.getLock("lock1");

        //2 尝试获取锁
        //(1) 阻塞一直等待直到获取到，获取锁之后，默认过期时间30s
        lock.lock();

        //(2) 获取到锁，锁过期时间10s
        // lock.lock(10,TimeUnit.SECONDS);

        //(3) 第一个参数获取锁等待时间
        //    第二个参数获取到锁，锁过期时间
        //        try {
        //            // true
        //            boolean tryLock = lock.tryLock(30, 10, TimeUnit.SECONDS);
        //        } catch (InterruptedException e) {
        //            throw new RuntimeException(e);
        //        }

        //3 编写业务代码
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
        String value = redisTemplate.opsForValue().get("num").toString();
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        redisTemplate.opsForValue().set("num", String.valueOf(++num));

        //4 释放锁
        lock.unlock();
    }
}